<script setup>
/**
 * 自定义表格使用手册
 * 使用时, 需传入columns、dataSource、showIndex、showOperation、loading
 * columns: 表头数据, 格式为[{title: '姓名', key: 'name', sortable: true, filterable: true}, ...]
 *     属性解析
 *         title: 表头显示的文字, 表头名字请勿雷同, 否则会导致显示筛选功能异常
 *         key: 表头对应的数据key
 *         sortable: 是否可排序, 默认为false
 *         filterable: 是否可筛选, 默认为false
 *         customer: 是否自定义表格显示的内容, 默认为false
 *         如果customer为true, 可以在使用组件时插入插槽, name为data-key, 其中key为column的key
 *         如果filterable为true, 可以在使用组件时插入插槽, name为filter-key, 其中key为column的key
 *     案例
 *         例如 columns: [{title: '姓名', key: 'name'}]
 *         会显示一列, 表头为[姓名], 对应的数据从dataSource中取属性(key)为name的值
 * dataSource: 表格行数据, 格式为[{name: '张三', age: 20, address: '上海市浦东新区'}, ...]
 *     属性解析
 *         属性名称与columns的key相同, 对应的值为对应的数据
 * showIndex: 是否显示编号, 默认为true
 * showOperation: 是否显示操作列, 默认为true
 * loading: 是否显示加载动画
 */
import { ref, onMounted, watch } from 'vue';
import { ElNotification } from 'element-plus'
import { bouncy } from 'ldrs'
bouncy.register() // 加载动画


const props = defineProps({
    loading: {
        type: Boolean,
        default: false
    },
    showOperation: {
        type: Boolean,
        default: true
    },
    showIndex: {
        type: Boolean,
        default: true
    },
    columns: {
        type: Array,
        default: () => [
            {
                title: '姓名',
                key: 'name',
                sortable: true,
                filterable: true
            },
            {
                title: '年龄',
                key: 'age',
                sortable: true,
                filterable: true
            },
            {
                title: '地址',
                key: 'address',
                sortable: true,
                filterable: true
            }
        ],
    },
    dataSource: {
        type: Array,
        default: () => [
            {
                name: '张三',
                age: 20,
                address: '上海市浦东新区',
            }
        ],
    },
});
const indexStatus = ref(props.showIndex); // 是否显示编号
const operationStatus = ref(props.showOperation); // 是否显示操作列
const tableColumns = ref(props.columns); // 显示的表头
const unTableColumns = ref([]); // 未显示的表头
const dialogVisible = ref(false)

// 表格的点击事件
const handleClick = (event) => {
    // 判断点击的元素是filter-icon 或是filter-icon的子元素
    if (!findDocumentIsInclude(event.target, 'filter-icon')) {
        const filterSlots = document.querySelectorAll('.filter-slot');
        filterSlots.forEach(slot => {
            slot.style.display = 'none';
        });
    }
}

const closeAllFilterSlot = () => {
    const filterSlots = document.querySelectorAll('.filter-slot');
    filterSlots.forEach(slot => {
        slot.style.display = 'none';
    });
}

// 查看点击的元素或其父元素是否包含指定className
const findDocumentIsInclude = (element, className) => {
    if (element.classList.contains(className)) {
        return true;
    }
    if (element.parentNode) {
        if (element.parentNode.classList.contains('my-table')) {
            return false;
        }
        return findDocumentIsInclude(element.parentNode, className);
    }
    return false;
}

// 搜索按钮背点击的效果
const filterIconClick = (event) => {
    // 点击后找到下一个兄弟元素中的filter-slot并控制显示与否
    const filterSlot = event.currentTarget.nextElementSibling;
    filterSlot.style.display = filterSlot.style.display === 'block' ? 'none' : 'block';
    // 同时需要关闭其他显示的filter-slot
    const filterSlots = document.querySelectorAll('.filter-slot');
    filterSlots.forEach(slot => {
        if (slot !== filterSlot) {
            slot.style.display = 'none';
        }
    });
}

// 修改显示的表头的对话框控制
const lsShow = ref([...tableColumns.value])
const lsUnshow = ref([...unTableColumns.value]);
const draggedItem = ref(null);
const draggedFrom = ref('');
// 拖动时触发
const handleDragStart = (item, from) => {
    draggedItem.value = item;
    draggedFrom.value = from;
};
// 拖动到区域鼠标松开时触发
const handleDrop = (to) => {
    if (draggedItem.value && draggedFrom.value !== to) {
        if (to === 'show') {
            // 从未显示区域拖到已显示区域
            lsUnshow.value = lsUnshow.value.filter(col => col.key !== draggedItem.value.key);
            lsShow.value.push(draggedItem.value);
        } else {
            // 从已显示区域拖到未显示区域
            if (lsShow.value.length <= 1) {
                ElNotification({
                    title: '提示',
                    message: '至少保留一个展示内容',
                    type: 'info',
                })
                return;
            }
            lsShow.value = lsShow.value.filter(col => col.key !== draggedItem.value.key);
            lsUnshow.value.push(draggedItem.value);
        }
    }
};
// 点击确定触发
const exchangeClick = () => {
    dialogVisible.value = false
    tableColumns.value = [...lsShow.value];
    unTableColumns.value = [...lsUnshow.value];
}
const reset = () => {
    lsShow.value = props.columns;
    lsUnshow.value = [];
}
const cancelChange = () => {
    dialogVisible.value = false
    lsShow.value = [...tableColumns.value];
    lsUnshow.value = [...unTableColumns.value];
}

// 监听传入的columns变化, 同时重置显示与未显示的表头
watch(() => props.columns, (newVal) => {
    tableColumns.value = [...newVal];
    unTableColumns.value = [];
    lsShow.value = [...newVal];
    lsUnshow.value = [];
});


// 计算加载动画的宽度
const loadingWidth = ref(0);
onMounted(() => {
    // 获取表格的宽度
    const tableElement = document.querySelector('.my-table');
    if (tableElement) {
        loadingWidth.value = tableElement.clientWidth;
    }
});
</script>

<template>
    <div class="my-table table-container" @click="handleClick" :style="{ overflow: loading ? 'hidden' : 'auto' }">
        <table class="styled-table">
            <thead>
                <tr class="header-row">
                    <th class="index-header" v-if="indexStatus"></th>
                    <th class="header" v-for="column in tableColumns" :key="column.key">
                        <div class="header-content">
                            {{ column.title }}
                            <span v-if="column.sortable" class="sort-icon">↑↓</span>
                            <span v-if="column.filterable" class="filter-icon">
                                <div class="filter-icon-body" @click="filterIconClick">🔍</div>
                                <div v-if="column.filterable" class="filter-slot">
                                    <slot :name="`filter-${column.key}`"></slot>
                                </div>
                            </span>
                        </div>
                    </th>
                    <th class="operation-header header" v-if="operationStatus">操作</th>
                    <th class="edit-header-btn" @click="dialogVisible = true">
                        <Edit color="red" />
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-if="loading" class="loading-height">

                    <div class="load-body">
                        <!-- <l-bouncy
                            size="120"
                            speed="1.25" 
                            color="black" 
                        ></l-bouncy> -->
                    </div>

                </tr>
                <tr v-else-if="!loading && dataSource.length === 0" class="loading-height">

                    <div class="load-body">
                        <el-empty :image-size="140" />
                    </div>

                </tr>
                <tr v-else-if="!loading && dataSource.length > 0" v-for="(row, index) in dataSource" :key="index">
                    <td class="index-row" v-if="indexStatus">{{ index }}</td>
                    <td class="data-row" v-for="column in tableColumns" :key="column.key">
                        <div v-if="column.customer" class="my-table-data-slot">
                            <slot :name="`data-${column.key}`" :row="row" :key="column.key" :data="row[column.key]">
                            </slot>
                        </div>
                        <div v-else class="my-table-data">
                            {{ row[column.key] }}
                        </div>
                    </td>
                    <td class="operation-row data-row" v-if="operationStatus">
                        <slot :name="`operation`" :row="row"></slot>
                    </td>
                </tr>
            </tbody>
            <div v-if="loading" class="loading" :style="{ width: loadingWidth + 'px' }">

                <div class="load-body">
                    <l-bouncy size="120" speed="1.25" color="black"></l-bouncy>
                </div>

            </div>
        </table>
        <el-dialog v-model="dialogVisible" title="编辑" width="800">
            <div class="edit-area">
                <div class="show-column">
                    <div class="show-column-label">
                        已显示的字段, 可通过拖动到下方区域不显示
                    </div>
                    <div class="show-column-area" @dragover.prevent @drop="handleDrop('show')">
                        <div class="column-item" v-for="column in lsShow" :key="column.key" draggable="true"
                            @dragstart="handleDragStart(column, 'show')">
                            {{ column.title }}
                        </div>
                    </div>
                </div>
                <div class="unshow-column">
                    <div class="unshow-column-label">
                        未显示的字段, 可通过拖动到上方区域显示
                    </div>
                    <div class="unshow-column-area" @dragover.prevent @drop="handleDrop('unshow')">
                        <div class="column-item" v-for="column in lsUnshow" :key="column.key" draggable="true"
                            @dragstart="handleDragStart(column, 'unshow')">
                            {{ column.title }}
                        </div>
                    </div>
                </div>
            </div>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="reset">恢复默认</el-button>
                    <el-button @click="cancelChange">取消</el-button>
                    <el-button type="primary" @click="exchangeClick">确定</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>

<style scoped lang="less">
.my-table {
    width: 100%;
    height: 100%;
    border: 1px solid #e8e8e8;
    border-radius: 4px;
    overflow: auto;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

    .data-row,
    .header {
        min-width: 100px;
    }

    .styled-table {
        width: 100%;
        border-collapse: collapse;
        position: relative;

        .loading {
            display: inline-block;
            width: 600px;
            height: 300px;
            position: absolute;
            top: 0;
            left: 0;
            background-color: rgba(255, 255, 255, 0.3);
            z-index: 10;
            backdrop-filter: blur(5px);
            --webkit-backdrop-filter: blur(5px);

            .load-body {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }
        }

        .loading-height {
            min-width: none;
            width: 100%;
            height: 300px;
            position: relative;
            background-color: #fff;

            .load-body {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }
        }

        .header-row {
            position: relative;
            background-color: #fafafa;

            th {
                padding: 12px 16px;
                text-align: left;
                font-weight: 500;
                color: rgba(0, 0, 0, 0.85);
                border-bottom: 1px solid #e8e8e8;
                border-right: 1px solid #e8e8e8;

                .header-content {
                    display: flex;
                    align-items: center;
                    gap: 8px;

                    .sort-icon {
                        color: #1890ff;
                        cursor: pointer;

                        &:hover {
                            opacity: 0.8;
                        }
                    }

                    .filter-icon {
                        position: relative;

                        .filter-icon-body {
                            cursor: pointer;

                            &:hover {
                                opacity: 0.8;
                            }
                        }

                        .filter-slot {
                            position: absolute;
                            display: none;
                            top: 32px;
                            left: 0;
                            padding: 8px;
                            background-color: #fff;
                            border: 1px solid #ddd;
                            border-radius: 4px;
                            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                            z-index: 10;
                        }
                    }
                }
            }

            .edit-header-btn {
                display: inline-block;
                width: 30px;
                height: 30px;
                padding: 0;
                flex-wrap: nowrap;
                position: sticky;
                right: 0;
                top: 0;
                cursor: pointer;
                background-color: #ffffff;

                img {
                    width: 25px;
                    height: 25px;

                    &:hover {
                        opacity: 0.8;
                    }
                }
            }
        }

        tbody tr:not(.loading) {
            &:nth-child(even) {
                background-color: #f9f9f9;
            }

            &:hover {
                background-color: #f0f0f0;
            }

            td {
                padding: 12px 16px;
                border-bottom: 1px solid #e8e8e8;
                border-right: 1px solid #e8e8e8;
            }
        }
    }

    .index-row,
    .index-header {
        min-width: 0px;
        width: 30px;
        text-align: center;
    }
}

.edit-area {

    .show-column,
    .unshow-column {
        width: 100%;
    }

    .show-column-label,
    .unshow-column-label {
        position: relative;
        width: 100%;
        height: 30px;
        padding: 0 15px;
        box-sizing: border-box;
        line-height: 30px;

        &::before {
            content: '';
            width: 3px;
            height: 15px;
            position: absolute;
            top: 50%;
            left: 0;
            transform: translateY(-50%);
            background-color: #ee5c34;
        }
    }

    .show-column-area,
    .unshow-column-area {
        width: 95%;
        margin: 20px auto;
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        padding: 20px 10px;
        border: 1px dashed #ee5c34;
        box-sizing: border-box;

        .column-item {
            padding: 2px 10px;
            border-radius: 4px;
            border: 1px solid #ee5c34;
            transition: all .4s linear;
            user-select: none;

            &:hover {
                background-color: #ee5c34;
                color: white;
            }
        }
    }

}
</style>